﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Red.GameData.Raw
{
    partial class DataProperty
    {
        /// <summary>
        /// Modify existing topmost property value
        /// This only works good for non-array properties
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="newValue">Value to set for the property</param>
        /// <returns>The data value holder with the new value (either existing or a new one)</returns>
        public DataValue Action_ModifyValue(string newValue)
        {
            // property does not have a value yet, define it
            if (Values.Count == 0)
            {
                var valEntry = new DataValue(this, Guid.NewGuid(), newValue);
                Values.Add(valEntry);
                MarkModified();

                return valEntry;
            }
            else
            {
                // modify existing value
                var valEntry = Values.Last();
                Debug.Assert(valEntry.Parent == this); // make sure value is from this data property
                valEntry.Value = newValue; // this calls MarkModified internally

                return valEntry;
            }
        }

        /// <summary>
        /// Add a new value to the data property (not modify, add)
        /// This is mostly useful for arrays
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="newValue">Value to ADD to the data property</param>
        /// <returns>The data value holder for the new value (a new one)</returns>
        public DataValue Action_AddValue(string newValue, Guid valueId)
        {
            // value already exists
            foreach (var entry in Values)
                if (entry.ID == valueId)
                    return null;

            // add new one
            var valEntry = new DataValue(this, valueId, newValue);
            Values.Add(valEntry);
            MarkModified();

            return valEntry;
        }

        /// <summary>
        /// Remove a data value entry that has a specified value ID
        /// This is mostly useful for arrays
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="valueID">ID for the data value entry that should be permamently removed from this data property</param>
        /// <returns>True if the value was in the property and was successfully removed, false if the data value was NOT in this property</returns>
        public bool Action_RemoveValue(Guid valueID)
        {
            // lame linear search
            foreach (var val in Values)
            {
                if (val.ID == valueID)
                {
                    Values.Remove(val);
                    MarkModified();
                    return true;
                }
            }

            // not found
            return false;
        }

        /// <summary>
        /// Change a specific value in the data property
        /// Data value entry must exist.
        /// If there's no data value entry with specific ID then it's NOT created.
        /// This is mostly useful for arrays
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="valueID">ID of the data value entry for which the value should be modified</param>
        /// <param name="newValue">New value to set for the data value entry</param>
        /// <returns>True if the change was successful or false if there was no such data value entry in this property</returns>
        public bool Action_SetValue(Guid valueID, string newValue)
        {
            foreach (var val in Values)
            {
                if (val.ID == valueID)
                {
                    if (val.Value != newValue)
                    {
                        val.Value = newValue;
                        MarkModified();
                    }

                    return true;
                }
            }

            // not found
            return false;
        }

        /// <summary>
        /// Change a specific value in the data property
        /// Data value entry must exist.
        /// If there's no data value entry with specific ID then it's created.
        /// This is mostly useful for arrays
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="valueID">ID of the data value entry for which the value should be modified</param>
        /// <param name="newValue">New value to set for the data value entry</param>
        public void Action_CreateValue(Guid valueID, string newValue)
        {
            foreach (var val in Values)
            {
                if (val.ID == valueID)
                {
                    if (val.Value != newValue)
                    {
                        val.Value = newValue;
                        MarkModified();
                    }

                    return;
                }
            }

            // not found, create new one
            Action_AddValue(newValue, valueID);
        }

        /// <summary>
        /// Make a specific data value entry in this data property suppressed
        /// This will prevent it from being collected in the value resolve phase thus it will have no impact on the final value list
        /// This is a very nice way to "delete" element from an inherited array without actually doing to much work
        /// This is mostly useful for arrays
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="valueId">ID of the data value entry that should be suppressed</param>
        /// <returns>True if it was possible to suppress the value, false otherwise</returns>
        public bool Action_SuppressValue(Guid valueId)
        {
            // well, empty guids are not supported
            if (valueId == Guid.Empty)
                return false;

            // already suppressed ?
            foreach (var suppressed in SupressedValues)
                if (suppressed == valueId)
                    return true; // already suppressed, that's not an error to suppress it twice

            // make sure to mark the object as modified
            SupressedValues.Add(valueId);
            MarkModified();
            return true;
        }

        /// <summary>
        /// Make a specific data value entry in this data property unsuppressed
        /// This will again allow it from being collected in the value resolve phase
        /// This is mostly useful for arrays
        /// NOTE: there's no way to know inside the DataProperty if the property is for an array or not
        /// NOTE: by convention all data modifying functions for properties are prefixed with Action_
        /// </summary>
        /// <param name="valueId">ID of the data value entry that should be unsuppressed</param>
        /// <returns>True if it was possible to unsuppress the value, false otherwise</returns>
        public bool Action_UnsuppressValue(Guid valueId)
        {
            if (valueId == Guid.Empty)
                return false;

            foreach (var suppressed in SupressedValues)
            {
                if (suppressed == valueId)
                {
                    SupressedValues.Remove(valueId);

                    // make sure to mark object as modified
                    MarkModified();
                    return true;
                }
            }

            // not found
            return false;
        }

        /// <summary>
        /// Move given element one step back in the array (toward larger array index)
        /// This works by swapping the element N with the element N+1
        /// </summary>
        /// <param name="valueId">ID of the value to move</param>
        /// <returns>True if moved, false if not moved or not found</returns>
        public bool Action_MoveValueBack(Guid valueId)
        {
            // find and move
            for (int i = 1; i < Values.Count; ++i)
            {
                if (Values[i-1].ID == valueId)
                {
                    var temp = Values[i];
                    Values[i] = Values[i - 1];
                    Values[i - 1] = temp;

                    MarkModified();
                    return true;
                }
            }

            // not moved
            return false;
        }

        /// <summary>
        /// Move given element one step forward in the array (toward smaller array index)
        /// This works by swapping the element N with the element N+1
        /// </summary>
        /// <param name="valueId">ID of the value to move</param>
        /// <returns>True if moved, false if not moved or not found</returns>
        public bool Action_MoveValueFront(Guid valueId)
        {
            // find and move
            for (int i = 1; i < Values.Count; ++i)
            {
                if (Values[i].ID == valueId)
                {
                    var temp = Values[i];
                    Values[i] = Values[i - 1];
                    Values[i - 1] = temp;
                    
                    MarkModified();                    
                    return true;
                }
            }

            // not moved
            return false;
        }
    }
}
